package wrappers

import (
	"errors"
	"gitee.com/hongzhaomin/hzm-common-go/strutil"
	"gitee.com/hongzhaomin/hzm-common-go/toolkit"
	"gitee.com/hongzhaomin/xormplus/base"
	"reflect"
	"strings"
	"sync"
	"xorm.io/builder"
	"xorm.io/xorm"
)

type Wrapper[Entity base.Domain] struct {
	entity   Entity
	selects  []string
	once     *sync.Once
	bd       *builder.Builder
	isOr     bool
	cond     builder.Cond
	orderBy  builder.Cond
	groupBys []string
	having   string
	limit    int
	start    int
	setMap   map[string]interface{} // 修改字段的map
}

func Query[Entity base.Domain]() *Wrapper[Entity] {
	var entity Entity
	if reflect.TypeOf(&entity).Elem().Kind() != reflect.Struct {
		panic(errors.New("必须为结构体"))
	}
	return &Wrapper[Entity]{
		entity: entity,
		once:   new(sync.Once),
		bd:     builder.MySQL(),
		isOr:   false,
		cond:   builder.NewCond(),
		setMap: make(map[string]interface{}),
	}
}

func (wp *Wrapper[Entity]) PutCond(cond builder.Cond) *Wrapper[Entity] {
	return wp.PutCondIfTrue(true, cond)
}

func (wp *Wrapper[Entity]) PutCondIfTrue(condition bool, cond builder.Cond) *Wrapper[Entity] {
	if condition {
		if wp.isOr {
			wp.cond = wp.cond.Or(cond)
			wp.isOr = false
		} else {
			wp.cond = wp.cond.And(cond)
		}
	}
	return wp
}

func (wp *Wrapper[Entity]) build(engine *xorm.Engine) {
	tableName := engine.TableName(wp.entity)
	if len(wp.selects) > 0 {
		wp.bd.Select(wp.selects...)
	} else {
		//wp.bd.Select(getColumnNames(wp.entity, engine.GetColumnMapper().Obj2Table)...)
		wp.bd.Select(wp.getColumnNames(engine)...)
	}
	wp.bd.From(tableName)
	wp.bd.Where(wp.cond)
	if len(wp.groupBys) > 0 {
		wp.bd.GroupBy(strings.Join(wp.groupBys, strutil.Comma))
	}
	if strutil.IsNotBlank(wp.having) {
		wp.bd.Having(wp.having)
	}
	if wp.orderBy != nil {
		wp.bd.OrderBy(wp.orderBy)
	}
	if wp.limit > 0 {
		wp.bd.Limit(wp.limit, wp.start)
	}
}

func getColumnNames(structVal any, convertName func(fileName string) string) []string {
	rt := reflect.TypeOf(structVal)
	n := rt.NumField()
	forceExcludeFields := make([]string, 0)
	for i := 0; i < n; i++ {
		field := rt.Field(i)
		if field.Anonymous {
			// 强制排除内嵌字段
			forceExcludeFields = append(forceExcludeFields, field.Name)
		}
	}
	return toolkit.GetAllFieldNames4CustomStrategy(structVal, convertName, forceExcludeFields...)
}

func (wp *Wrapper[Entity]) getColumnNames(engine *xorm.Engine) []string {
	tableInfo, err := engine.TableInfo(wp.entity)
	if err != nil {
		return getColumnNames(wp.entity, engine.GetColumnMapper().Obj2Table)
	}

	colNames := make([]string, len(tableInfo.Columns()))
	for i, column := range tableInfo.Columns() {
		colNames[i] = column.Name
	}
	return colNames
}

func (wp *Wrapper[Entity]) GetBuilder(engine *xorm.Engine) *builder.Builder {
	// 只构建一次
	wp.once.Do(func() {
		wp.build(engine)
	})
	return wp.bd
}

func (wp *Wrapper[Entity]) GetCond() builder.Cond {
	return wp.cond
}

func (wp *Wrapper[Entity]) GetOrderBy() builder.Cond {
	return wp.orderBy
}

func (wp *Wrapper[Entity]) GetSelect() []string {
	return wp.selects
}

func (wp *Wrapper[Entity]) GetSetMap() map[string]any {
	return wp.setMap
}

// ================================== 以下为构造wrapper方法 ================================

func (wp *Wrapper[Entity]) Select(col ...string) *Wrapper[Entity] {
	wp.selects = append(wp.selects, col...)
	return wp
}

func (wp *Wrapper[Entity]) AndSub(andFunc func(subWrapper *Wrapper[Entity])) *Wrapper[Entity] {
	return wp.AndSubIfTrue(true, andFunc)
}

func (wp *Wrapper[Entity]) AndSubIfTrue(condition bool, andFunc func(subWrapper *Wrapper[Entity])) *Wrapper[Entity] {
	if condition {
		subWrapper := Query[Entity]()
		andFunc(subWrapper)
		wp.cond = wp.cond.And(subWrapper.cond)
	}
	return wp
}

func (wp *Wrapper[Entity]) Or() *Wrapper[Entity] {
	wp.isOr = true
	return wp
}

func (wp *Wrapper[Entity]) OrSub(orFunc func(subWrapper *Wrapper[Entity])) *Wrapper[Entity] {
	return wp.OrSubIfTrue(true, orFunc)
}

func (wp *Wrapper[Entity]) OrSubIfTrue(condition bool, orFunc func(subWrapper *Wrapper[Entity])) *Wrapper[Entity] {
	if condition {
		subWrapper := Query[Entity]()
		orFunc(subWrapper)
		wp.cond = wp.cond.Or(subWrapper.cond)
	}
	return wp
}

func (wp *Wrapper[Entity]) Eq(col string, val any) *Wrapper[Entity] {
	return wp.EqIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) EqIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Eq{col: val})
}

func (wp *Wrapper[Entity]) EqMap(eqMap map[string]interface{}) *Wrapper[Entity] {
	return wp.EqMapIfTrue(true, eqMap)
}

// EqMapIfTrue 可以将多个col进行and拼接：col1 = ? and col2 = ?...
// 另外，如果map的val是切片类型，则会使用in查询：col1 = ? and col2 = ? and col3 in(v1, v2 ...)
func (wp *Wrapper[Entity]) EqMapIfTrue(condition bool, eqMap map[string]interface{}) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Eq(eqMap))
}

func (wp *Wrapper[Entity]) Neq(col string, val any) *Wrapper[Entity] {
	return wp.NeqIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) NeqIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Neq{col: val})
}

func (wp *Wrapper[Entity]) In(col string, vals ...any) *Wrapper[Entity] {
	return wp.InIfTrue(true, col, vals...)
}

// InSql in查询条件可拼接sql
// 例如：builder.In("a", builder.Expr("select id from x where name > ?", "b"))
func (wp *Wrapper[Entity]) InSql(col string, sql string) *Wrapper[Entity] {
	return wp.InIfTrue(true, col, builder.Expr(sql))
}

func (wp *Wrapper[Entity]) InIfTrue(condition bool, col string, vals ...any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.In(col, vals...))
}

func (wp *Wrapper[Entity]) NotIn(col string, vals ...any) *Wrapper[Entity] {
	return wp.NotInIfTrue(true, col, vals...)
}

func (wp *Wrapper[Entity]) NotInSql(col string, sql string) *Wrapper[Entity] {
	return wp.NotInIfTrue(true, col, builder.Expr(sql))
}

func (wp *Wrapper[Entity]) NotInIfTrue(condition bool, col string, vals ...any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.NotIn(col, vals...))
}

func (wp *Wrapper[Entity]) Like(col string, val string) *Wrapper[Entity] {
	return wp.LikeIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) LikeLeft(col string, val string) *Wrapper[Entity] {
	return wp.LikeLeftIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) LikeRight(col string, val string) *Wrapper[Entity] {
	return wp.LikeRightIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) LikeIfTrue(condition bool, col string, val string) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Like{col, val})
}

func (wp *Wrapper[Entity]) LikeLeftIfTrue(condition bool, col string, val string) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Like{col, "%" + val})
}

func (wp *Wrapper[Entity]) LikeRightIfTrue(condition bool, col string, val string) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Like{col, val + "%"})
}

func (wp *Wrapper[Entity]) Lt(col string, val any) *Wrapper[Entity] {
	return wp.LtIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) Le(col string, val any) *Wrapper[Entity] {
	return wp.LeIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) Gt(col string, val any) *Wrapper[Entity] {
	return wp.GtIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) Ge(col string, val any) *Wrapper[Entity] {
	return wp.GeIfTrue(true, col, val)
}

func (wp *Wrapper[Entity]) LtIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Lt{col: val})
}

func (wp *Wrapper[Entity]) LeIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Lte{col: val})
}

func (wp *Wrapper[Entity]) GtIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Gt{col: val})
}

func (wp *Wrapper[Entity]) GeIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.Gte{col: val})
}

func (wp *Wrapper[Entity]) IsNull(col string) *Wrapper[Entity] {
	return wp.IsNullIfTrue(true, col)
}

func (wp *Wrapper[Entity]) IsNullIfTrue(condition bool, col string) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.IsNull{col})
}

func (wp *Wrapper[Entity]) NotNull(col string) *Wrapper[Entity] {
	return wp.NotNullIfTrue(true, col)
}

func (wp *Wrapper[Entity]) NotNullIfTrue(condition bool, col string) *Wrapper[Entity] {
	return wp.PutCondIfTrue(condition, builder.NotNull{col})
}

func (wp *Wrapper[Entity]) Limit(limit int, start ...int) *Wrapper[Entity] {
	wp.limit = limit
	if len(start) > 0 {
		wp.start = start[0]
	}
	return wp
}

func (wp *Wrapper[Entity]) GroupBy(cols ...string) *Wrapper[Entity] {
	return wp.GroupByIfTrue(true, cols...)
}

func (wp *Wrapper[Entity]) GroupByIfTrue(condition bool, cols ...string) *Wrapper[Entity] {
	if condition && len(cols) > 0 {
		wp.groupBys = append(wp.groupBys, cols...)
	}
	return wp
}

func (wp *Wrapper[Entity]) Having(sqlHaving string, vals ...string) *Wrapper[Entity] {
	return wp.HavingIfTrue(true, sqlHaving, vals...)
}

// HavingIfTrue ( sql语句 )
// 例1: having("sum(age) > 10 and sum(age) < 30")
// 例2: having("sum(age) > ? and sum(age) < ?", 10, 30)
func (wp *Wrapper[Entity]) HavingIfTrue(condition bool, sqlHaving string, vals ...string) *Wrapper[Entity] {
	if condition && strutil.IsNotBlank(sqlHaving) {
		if len(vals) > 0 {
			expr := builder.Expr(sqlHaving, vals)
			sqlFragments, err := builder.ToBoundSQL(expr)
			if err == nil {
				panic(err)
			}
			wp.having = sqlFragments
		} else {
			wp.having = sqlHaving
		}
	}
	return wp
}

func (wp *Wrapper[Entity]) OrderByAsc(cols ...string) *Wrapper[Entity] {
	return wp.OrderByAscIfTrue(true, cols...)
}

func (wp *Wrapper[Entity]) OrderByDesc(cols ...string) *Wrapper[Entity] {
	return wp.OrderByDescIfTrue(true, cols...)
}

// OrderByAscIfTrue 排序：ORDER BY 字段, ... ASC
// 例: orderByAsc("id", "name")
func (wp *Wrapper[Entity]) OrderByAscIfTrue(condition bool, cols ...string) *Wrapper[Entity] {
	if condition && len(cols) > 0 {
		orderByAsc := []string{
			strings.Join(cols, strutil.Comma),
			base.Asc,
		}
		wp.orderBy = builder.Expr(strings.Join(orderByAsc, strutil.Space))
	}
	return wp
}

func (wp *Wrapper[Entity]) OrderByDescIfTrue(condition bool, cols ...string) *Wrapper[Entity] {
	if condition && len(cols) > 0 {
		orderByDesc := []string{
			strings.Join(cols, strutil.Comma),
			base.Desc,
		}
		wp.orderBy = builder.Expr(strings.Join(orderByDesc, strutil.Space))
	}
	return wp
}

func (wp *Wrapper[Entity]) Set(col string, val any) *Wrapper[Entity] {
	wp.setMap[col] = val
	return wp
}

func (wp *Wrapper[Entity]) SetIfTrue(condition bool, col string, val any) *Wrapper[Entity] {
	if condition && strutil.IsNotBlank(col) {
		wp.setMap[col] = val
	}
	return wp
}
